﻿

#region Usings

using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Monitoring;
using ScrumTable.Common.Security;
using ScrumTable.Common.Threading;
using ScrumTable.DL.Data.Generic.Cache;
using ScrumTable.DL.Data.TFS.Bulk.Properties;
using ScrumTable.DL.Data.TFS.Bulk.Services.IterationHandling;
using ScrumTable.DL.Data.TFS.Services.FileSystem;
using ScrumTable.DL.Data.TFS.Services.Reporting;
using ScrumTable.DL.Data.TFS.Services.TeamFoundation;

#endregion

namespace ScrumTable.DL.Data.TFS.Bulk.Services
{
    /// <summary>
    /// This class is used to load all data below a given project.
    /// </summary>
    internal partial class InitialDataCacheLoader : DisposeBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const int ProjectLoadPartsTotal = 8;
        private const string ProjectLoadPart = "Project";
        private const string MemberLoadPart = "Members";
        private const string UserStoryLoadPart = "User Stories";
        private const string TaskLoadPart = "Tasks";
        private const string BugLoadPart = "Bugs";
        private const string ReportLoadPart = "Reports";

        private readonly ConnectionInformationManager _connectionCtx;
        private readonly Uri _backEndSystemUri;
        private readonly CacheContext _chache;

        private readonly IDataImportMonitor _importMonitor;
        private readonly WorkItemDataLoader _workItemDataLoader;
        private readonly ReportDataLoader _reportDataLoader;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new InitialDataCacheLoader instance, no public constructor
        /// needed.
        /// </summary>
        internal InitialDataCacheLoader(ConnectionInformationManager connectionCtx, Uri backEndSystemUri, CacheContext cache, IDataImportMonitor importMonitor)
        {
            PreCondition.AssertNotNull(importMonitor, "importMonitor");
            PreCondition.AssertNotNull(backEndSystemUri, "backEndSystemUri");
            PreCondition.AssertNotNull(connectionCtx, "connectionCtx");

            _connectionCtx = connectionCtx;
            _backEndSystemUri = backEndSystemUri;
            _chache = cache;
            _importMonitor = importMonitor;
            _workItemDataLoader = new WorkItemDataLoader(_chache.ElementFactory);
            _reportDataLoader = new ReportDataLoader(_chache.ElementFactory);
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        internal void LoadData(ValueChain<string> projectIdChain)
        {
            PreCondition.AssertNotNull(projectIdChain, "projectIdChain");

            new AsyncCall(() => StartLoadProjectData(projectIdChain)).Execute();
        }


        //--------------------------------------------------------------------
        // Load Project Data
        //--------------------------------------------------------------------

        private void StartLoadProjectData(ValueChain<string> projectIdChain)
        {
            _importMonitor.StartSubProgress(ProjectLoadPart);

            try { LoadProjectData(projectIdChain); }
            catch (Exception e) { _importMonitor.EndSubProgress(ProjectLoadPart, e); }
        }

        private void LoadProjectData(ValueChain<string> projectIdChain)
        {
            // 1. part
            DataLoadContext context = InitDataLoadContext(_chache.Projects[projectIdChain.Top]);
            _importMonitor.ReportProgress(ProjectLoadPart, 1 /* part */, ProjectLoadPartsTotal);

            // 2. part (members)
            StartLoadMemberData(context);
            _importMonitor.ReportProgress(ProjectLoadPart, 2 /* part */, ProjectLoadPartsTotal);


            // async load part

            // 8. part ( -> load data into cache)
            Action<Exception> cacheImport =
                e =>
                {
                    if (e == null) { context.CacheProject.ImportData(context.LoadedIterations, context.LoadedReports, context.LoadedWorkItems); }
                    _importMonitor.ReportProgress(ProjectLoadPart, 8 /* part */, ProjectLoadPartsTotal);
                    _importMonitor.EndSubProgress(ProjectLoadPart, e);
                };

            // 7. part (bugs)
            Action<Exception> bugLoad =
                e =>
                {
                    if (e == null) { StartLoadBugData(context, cacheImport); }
                    _importMonitor.ReportProgress(ProjectLoadPart, 7 /* part */, ProjectLoadPartsTotal);
                    if (e != null) { cacheImport(e); }
                };

            // 6. part (tasks)
            Action<Exception> taskLoad =
                e =>
                {
                    if (e == null) { StartLoadTaskData(context, bugLoad); }
                    _importMonitor.ReportProgress(ProjectLoadPart, 6 /* part */, ProjectLoadPartsTotal);
                    if (e != null) { bugLoad(e); }
                };

            // 5. part (user stories)
            Action<Exception> userStoryLoad =
                e =>
                {
                    if (e == null) { StartLoadUserStoryData(context, taskLoad); }
                    _importMonitor.ReportProgress(ProjectLoadPart, 5 /* part */, ProjectLoadPartsTotal);
                    if (e != null) { taskLoad(e); }
                };

            // 4. part (iterations)
            Action<Exception> iterationLoad =
                e =>
                {
                    if (e == null) { StartLoadIterationData(context, userStoryLoad); }
                    _importMonitor.ReportProgress(ProjectLoadPart, 4 /* part */, ProjectLoadPartsTotal);
                    if (e != null) { userStoryLoad(e); }
                };

            // 3. part (reports)
            StartLoadReportData(context, iterationLoad);
            _importMonitor.ReportProgress(ProjectLoadPart, 3 /* part */, ProjectLoadPartsTotal);
        }


        //--------------------------------------------------------------------
        // Load Member Data
        //--------------------------------------------------------------------

        // PARTIAL CLASS:
        //  StartLoadMemberData(DataLoadContext context) must be specified in a different file 
        partial void StartLoadMemberData(DataLoadContext context);

        //--------------------------------------------------------------------
        // Load User Story Data
        //--------------------------------------------------------------------

        private void StartLoadUserStoryData(DataLoadContext context, Action<Exception> callback)
        {
            try { LoadUserStoryData(context, callback); }
            catch (Exception e) { _importMonitor.EndSubProgress(UserStoryLoadPart, e); callback(e); }
        }

        private void LoadUserStoryData(DataLoadContext context, Action<Exception> callback)
        {
            AsyncDataLoader<WorkItemDataLoadToken<CacheWorkItem>> loader = new AsyncDataLoader<WorkItemDataLoadToken<CacheWorkItem>>(
                this,
                _importMonitor,
                UserStoryLoadPart,

                /* callback method if completed */
                result => callback(((DataResult)result).Error));

            IDictionary<int, WorkItemDataLoadToken<CacheWorkItem>> loadTokens = _workItemDataLoader.QueryWorkItemTokens<CacheWorkItem>(
                context.TfsContext.WorkItems,
                context.CacheProject,
                context.CacheContext.Schema.UserStorySchema.SchemaMappedName);

            loader.LoadAsync(
                /* contains the tokens with the information about the data to load */
                loadTokens.Values,

                /* async part, should contain long running process; force load all data */
                t => _workItemDataLoader.LoadWorkItemByToken(context.TfsContext.WorkItems, t),

                /* context sync part, should only contain an import/assignment  */
                t =>
                {
                    if (t.Result != null)
                    {
                        context.LoadedWorkItems.Add(t.Result.Id, t.Result);
                    }
                });
        }


        //--------------------------------------------------------------------
        // Load Task Data
        //--------------------------------------------------------------------

        private void StartLoadTaskData(DataLoadContext context, Action<Exception> callback)
        {
            try { LoadTaskData(context, callback); }
            catch (Exception e) { _importMonitor.EndSubProgress(TaskLoadPart, e); callback(e); }
        }

        private void LoadTaskData(DataLoadContext context, Action<Exception> callback)
        {
            AsyncDataLoader<WorkItemDataLoadToken<CacheWorkItem>> loader = new AsyncDataLoader<WorkItemDataLoadToken<CacheWorkItem>>(
                this,
                _importMonitor,
                TaskLoadPart,

                /* callback method if completed */
                result => callback(((DataResult)result).Error));

            IDictionary<int, WorkItemDataLoadToken<CacheWorkItem>> loadTokens = _workItemDataLoader.QueryWorkItemTokens<CacheWorkItem>(
                context.TfsContext.WorkItems,
                context.CacheProject,
                context.CacheContext.Schema.TaskSchema.SchemaMappedName);

            loader.LoadAsync(
                /* contains the tokens with the information about the data to load */
                loadTokens.Values,

                /* async part, should contain long running process; force load all data */
                t => _workItemDataLoader.LoadWorkItemByToken(context.TfsContext.WorkItems, t),

                /* context sync part, should only contain an import/assignment  */
                t =>
                {
                    if (t.Result != null)
                    {
                        context.LoadedWorkItems.Add(t.Result.Id, t.Result);
                    }
                });
        }


        //--------------------------------------------------------------------
        // Load Bug Data
        //--------------------------------------------------------------------

        private void StartLoadBugData(DataLoadContext context, Action<Exception> callback)
        {
            try { LoadBugData(context, callback); }
            catch (Exception e) { _importMonitor.EndSubProgress(BugLoadPart, e); callback(e); }
        }

        private void LoadBugData(DataLoadContext context, Action<Exception> callback)
        {
            AsyncDataLoader<WorkItemDataLoadToken<CacheWorkItem>> loader = new AsyncDataLoader<WorkItemDataLoadToken<CacheWorkItem>>(
                this,
                _importMonitor,
                BugLoadPart,

                /* callback method if completed */
                result => callback(((DataResult)result).Error));

            IDictionary<int, WorkItemDataLoadToken<CacheWorkItem>> loadTokens = _workItemDataLoader.QueryWorkItemTokens<CacheWorkItem>(
                context.TfsContext.WorkItems,
                context.CacheProject,
                context.CacheContext.Schema.BugSchema.SchemaMappedName);

            loader.LoadAsync(
                /* contains the tokens with the information about the data to load */
                loadTokens.Values,

                /* async part, should contain long running process; force load all data */
                t => _workItemDataLoader.LoadWorkItemByToken(context.TfsContext.WorkItems, t),

                /* context sync part, should only contain an import/assignment  */
                t =>
                {
                    if (t.Result != null)
                    {
                        context.LoadedWorkItems.Add(t.Result.Id, t.Result);
                    }
                });
        }


        //--------------------------------------------------------------------
        // Load Iteration Data
        //--------------------------------------------------------------------

        private void StartLoadIterationData(DataLoadContext context, Action<Exception> callback)
        {
            try { LoadIterationData(context, callback); }
            catch (Exception e) { _importMonitor.EndSubProgress(IterationLoaderBase.IterationLoadPart, e); callback(e); }
        }

        private void LoadIterationData(DataLoadContext context, Action<Exception> callback)
        {
            IterationLoaderBase loader = IterationHandlingFactory.CreateIterationLoader(context.CacheContext.Schema.IterationSchema);
            loader.StartLoadIterationData(
                _importMonitor,
                _workItemDataLoader,
                context,
                callback);
        }


        //--------------------------------------------------------------------
        // Load Report Data
        //--------------------------------------------------------------------

        private void StartLoadReportData(DataLoadContext context, Action<Exception> callback)
        {
            try { LoadReportData(context, callback); }
            catch (Exception e) { _importMonitor.EndSubProgress(ReportLoadPart, e); callback(e); }
        }

        private void LoadReportData(DataLoadContext context, Action<Exception> callback)
        {
            // load reports in an async manner
            AsyncDataLoader<ReportDataLoadToken> loader = new AsyncDataLoader<ReportDataLoadToken>(
                this,
                _importMonitor,
                ReportLoadPart,

                /* callback method if completed */
                result => callback(((DataResult)result).Error));

            IList<ReportDataLoadToken> loadTokens = context.ReportingContext.GetReportList().Convert(
                rawReport => new ReportDataLoadToken(
                    rawReport,
                    context.ReportingContext.GetAbsolutPathFromReportPath(rawReport.Path)));

            loader.LoadAsync(
                /* contains the tokens with the information about the data to load */
                loadTokens,

                /* async part, should contain long running process; force load all data */
                t => t.Result = _reportDataLoader.LoadReport(
                    t.Reference,
                    t.FullReportPath,
                    RsContext.PostCommandArguments,
                    context.ReportingContext.GetItemParameters(t.Reference)),

                /* context sync part, should only contain an import/assignment  */
                t =>
                {
                    if (t.Result != null)
                    {
                        context.LoadedReports.Add(t.Result);
                    }
                });
        }


        //--------------------------------------------------------------------
        // Utilities
        //--------------------------------------------------------------------

        private DataLoadContext InitDataLoadContext(CacheProject project)
        {
            // establish connection
            TfsServerWrapper tfsInstance = new TfsConnector(_connectionCtx.Selected, _backEndSystemUri).Connect();

            try
            {
                if (!tfsInstance.EnsureConnected())
                    throw new InvalidOperationException(Resources.ExcCacheConnectionUnavailable);

                return CreateDataLoadContext(project, tfsInstance);
            }
            catch (Exception)
            {
                if (tfsInstance != null)
                    tfsInstance.Dispose();
                throw;
            }
        }

        // PARTIAL CLASS:
        //  CreateDataLoadContext(CacheProject project, TfsServerWrapper tfsInstance) must be specified in a different file 

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}